上一章節我們講完 policy 的設計,這一章節我們來看更新方法
def select_action(state):
state = torch.from_numpy(state).float().unsqueeze(0)
probs = policy(state)
m = Categorical(probs)
action = m.sample()
policy.saved_log_probs.append(m.log_prob(action))
return action.item()
select_action 這邊是會接 policy 的輸出機率,接著透過 Categorical 這個 class 得到一個機率分佈 m,接著使用 m 去採樣,它會根據機率的分佈輸出一個結果,如果動作有兩個,那不是 1 就是 0,更直觀一點來看,如果分別是 30% 與 70% 的機率,我們這邊跑100次,則會有很大的可能,會出現30次與70次。
log_prob 這邊是針對於我們得到的結果,反推去算該機率的 log_prob,有log_prob值,我們就可以對該動作就近行反向傳播的訓練。
def finish_episode():
R = 0
policy_loss = []
returns = deque()
for r in policy.rewards[::-1]:
R = r + args.gamma * R
returns.appendleft(R)
returns = torch.tensor(returns)
returns = (returns - returns.mean()) / (returns.std() + eps)
for log_prob, R in zip(policy.saved_log_probs, returns):
policy_loss.append(-log_prob * R)
optimizer.zero_grad()
policy_loss = torch.cat(policy_loss).sum()
policy_loss.backward()
optimizer.step()
del policy.rewards[:]
del policy.saved_log_probs[:]
在這裡 R 為我們最終得到的獎勵值,再第66行可以看到
for r in policy.rewards[::-1]:
這個意思是當每次回合結束後,就會把最後得到的 reward 往回推做計算,這種把最終獎勵往回推的方法,會讓整個路徑的動作,在訓練的時候也會增大機率。還有另一種方式是每回合得到獎勵,我們就以每一回合得到的值做訓練,但不做反推。
returns = (returns - returns.mean()) / (returns.std() + eps)
這邊的話是對於我們要計算的 returns值做normalize,基本上除了 rewards,input data 也會做歸一化,好處就是降低噪聲跟極端值的影響。
policy_loss.append(-log_prob * R)
最後這邊 log_prob * R ,就是定義好 policy 模型優化的方向,log_prob 就是我們剛提到,輸出機率的 log值, R就是獎勵,根據大小,也會影響每個動作的更新幅度。那這邊會加上負,是因為一般網路的優化都是要透過梯度下降,減少loss。強化學習則是透過這個負號,讓模型梯度上升,優化得到好 reward 的機率值,最終最大化整個獎勵。
核心的部分都介紹完了!剩下的比較雜的就請讀者自己看看,如果對gym有問題,也可以觀看筆者之前的教學,那到這邊,大家應該都是可以很順利的使用 Reinforce 的方法,完成平衡小車這個 task了。
其實 policy 的更新非常直觀,主要是說 reward 怎麼設計,合理性與更新幅度,是需要挑戰的。那如果讀者有再延伸去看 PPO 的研究,其實很多的 tricks 就是針對收斂上遇到的不穩定性,使它收斂不要更加 smooth 的方法。因為強化學習要挑戰的問題很多,像是最佳化問題、探索與利用問題,或擁有多個子目標… 有很多具有挑戰也是仍在克服的問題,但筆者認為 RL 真的是一個非常有趣的領域。